﻿using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using KC.Service.Account;
using KC.DTO.Account;
using KC.Web.SSO.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;

namespace KC.Web.SSO.Controllers
{
    //[Route("api/[controller]")]
    public class JwtController : Controller
    {
        #region Private Members
        // JWT-related members
        private TimeSpan TokenExpiration;//default: 10 minutes
        private SigningCredentials SigningCredentials;
        private IAccountService _userRepository;
        #endregion Private Members

        #region Static Members
        private static readonly string PrivateKey = "my_PrivateKey";
        public static readonly SymmetricSecurityKey SecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey));
        public static readonly string Issuer = "my_Issuer";
        public static readonly string Audience = "my_Audience";
        #endregion Static Members

        #region Constructor
        // I have used Autofac in the Startup.cs for dependency injection)
        public JwtController(IAccountService userRepository)
        {
            _userRepository = userRepository;
            // Instantiate JWT-related members
            TokenExpiration = TimeSpan.FromMinutes(10);
            SigningCredentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);
            // Instantiate through Dependency Injection with Autofact
            //DbContext = new TenantContext();
        }
        #endregion Constructor

        [HttpGet]
        public JsonResult JwtToken()
        {
            var jwt = new JwtRequestViewModel();
            jwt.GrantType = "password";
            jwt.UserName = "test@cfwin.com";
            jwt.Password = "P@ssw0d";
            jwt.ClientId = "my_Issuer";

            return Json(jwt);
        }

        #region Public Methods 
        // Manages the request for a new authentication or the refresh of an 
        // already established one
        [HttpPost/*("token")*/]
        public async Task<IActionResult> Authentication([FromBody]JwtRequestViewModel jwt)
        {
            if (ModelState.IsValid)
            {
                string grantType = jwt.GrantType;
                if (grantType == "password")
                {
                    string userName = jwt.UserName;
                    string password = jwt.Password;

                    // Password check required
                    var user = _userRepository.GetUserByUserName(userName);

                    // Check if user is expired (check the ExpireDate property)
                    if (UserExpired(user))
                        return BadRequest($"Account of {user.UserName} expired!");

                    if (UserEnabled(user))
                        return await GenerateToken(user);
                    else
                        return BadRequest("User name or password invalid.");
                }
                else if (grantType == "refresh_token")
                {
                    string userName = jwt.UserName;

                    // Refresh token (no password check required)
                    var user = _userRepository.GetUserByUserName(userName);

                    // Check if user is expired (check the ExpireDate property)
                    if (UserExpired(user))
                        return BadRequest($"Account of {user.UserName} expired!");

                    string token = jwt.Token;
                    //if (token == user.Token)
                    {
                        // Generate token and send it via a json-formatted string
                        return await GenerateToken(user);
                    }
                    //else
                    //{
                    //    return BadRequest("User token invalid.");
                    //}
                }
                else
                    return BadRequest("Authentication type invalid.");
            }
            else
                return BadRequest("Request invalid.");
        }

        #endregion Public Methods

        #region Private Methods
        private bool UserExpired(UserDTO utente)
        {
            if (utente != null && utente.LockoutEndDateUtc.HasValue)
                return utente.LockoutEndDateUtc.Value.CompareTo(DateTime.Now) < 0;
            return true;
        }

        private bool UserEnabled(UserDTO utente)
        {
            if (utente != null)
                return utente.LockoutEnabled == true;
            return false;
        }

        private JsonSerializerSettings DefaultJsonSettings
        {
            get
            {
                return new JsonSerializerSettings()
                {
                    Formatting = Formatting.Indented
                };
            }
        }

        private async Task<IActionResult> GenerateToken(UserDTO user)
        {
            try
            {
                if (user != null)
                {
                    var handler = new JwtSecurityTokenHandler();
                    DateTime newTokenExpiration = DateTime.Now.Add(TokenExpiration);

                    ClaimsIdentity identity = new ClaimsIdentity(
                      new GenericIdentity(user.UserName, "TokenAuth"),
                      new[] { new Claim("ID", user.UserId.ToString()) }
                    );

                    var securityToken = handler.CreateToken(new SecurityTokenDescriptor
                    {
                        Issuer = JwtController.Issuer,
                        Audience = JwtController.Audience,
                        SigningCredentials = SigningCredentials,
                        Subject = identity,
                        Expires = newTokenExpiration
                    });
                    string encodedToken = handler.WriteToken(securityToken);

                    // Update token data on database
                    //await _userRepository.UpdateTokenData(user.UserName, encodedToken,
                    //  newTokenExpiration);
                    // Build the json response 
                    // (I use Automapper to maps an object into another object)
                    var jwtResponse = new JwtResponseViewModel();
                    jwtResponse.Id = user.UserId;
                    jwtResponse.UserName = user.UserName;
                    jwtResponse.AccessToken = encodedToken;
                    jwtResponse.Expiration = (int)TokenExpiration.TotalSeconds;
                    return Ok(jwtResponse);
                }
                return NotFound();
            }
            catch (Exception e)
            {
                return BadRequest(e.Message);
            }
        }
        #endregion
    }
}